package com.zkh.myutils.net.ftp;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.zkh.myutils.code.Charsets;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.net.ClientFactory;
import com.zkh.myutils.net.utils.NetUtils;
import com.zkh.myutils.net.utils.Reply;
import com.zkh.myutils.utils.UtilsException;

/**
 * FTP客户端实例
 * @author zkh
 */
public class FTPClient extends FTP {
	//FTP主机
	private String hostname;
	//FTP端口
	private int port = FTP.DEFAULT_PORT;
	//登录帐号
	private String username;
	//登录密码
	private String password;
	//匿名访问
	private boolean anonymous;
	
	/**
	 * 构造方法
	 * @param hostname 主机
	 * @param port 端口
	 * @param username 用户
	 * @param password 密码
	 */
	public FTPClient(String hostname, int port, String username, String password) {
		super();
		this.hostname = hostname;
		this.port = port;
		this.username = username;
		this.password = password;
	}
	
	/**
	 * 初始化FTP连接
	 * 
	 * @throws IOException
	 */
	public void init() throws IOException{
		//失效
		if(!isAvailable()){
			//设置编码
			this.setCharset(Charsets.UTF_8);
			//打印记录
			Logger.debug("开始连接FTP服务器");
			//如果启用SSL，设置SSL Socket工厂
			if(ClientFactory.isEmailEnableSSL()) {
				//设置SSL Socket工厂
				this.setSocketFactory(NetUtils.getSSLDefaultSocketFactory());
			}
			//连接主机
			this.connect(hostname, port);
			//匿名
			if(anonymous) {
				this.login("anonymous", null);
			}//非匿名
			else {
				this.login(username, password);
			}
			//检查是否登录成功
			int replyCode = this.getReplyCode();
			//连接失败，报错
			if(!Reply.isPositiveCompletion(replyCode)){
				throw new UtilsException("FTP服务器连接失败");
			}else{
				Logger.debug("FTP服务器连接成功");
			}
		}
	}

	/**
	 * 设置匿名访问，默认不匿名
	 * @param anonymous 是否匿名
	 */
	public void setAnonymous(boolean anonymous) {
		this.anonymous = anonymous;
	}
	
	/**
	 * 获取当前是否采用匿名方式访问
	 * @return
	 */
	public boolean isAnonymous() {
		return anonymous;
	}

	/**
	 * 检测FTP是否可以正常连接
	 * @return
	 */
	public boolean checkConnection(){
		//获取连接
		try {
			//获取连接
			init();
			//返回连接成功状态
			return true;
		} catch (IOException | UtilsException e) {
			//打印错误信息
			Logger.error(e);
			//如果连接创建成功，则注销
			logoutQuietly();
			//返回状态
			return false;
		}
	}
	
	/**
	 * 上传文件到FTP。（指针跟随上传目录移动）
	 * @param absoluteFile 要上传的文件的绝对路径
	 * @param filepathname 保存的文件名。
	 */
	public void uploadFileTo(String absoluteFile, String filepathname) throws IOException{
		//解析名称
		String[] pathName = parseFilepath(filepathname);
		//执行上传操作
		uploadFileTo(absoluteFile, pathName[0], pathName[1]);
	}
	
	/**
	 * 上传文件到FTP。（指针跟随上传目录移动）
	 * @param absoluteFile 文件绝对路径
	 * @param directory 保存目录
	 */
	public void uploadFileToDirectory(String absoluteFile, String directory) throws IOException{
		uploadFileTo(absoluteFile, directory, absoluteFile.substring(absoluteFile.lastIndexOf('/')+1));
	}
	
	/**
	 * 上传文件到FTP。（指针跟随上传目录移动）
	 * @param absoluteFile 要上传的文件的绝对路径。
	 * @param pathname 要上传到FTP的目录。
	 * @param filename 上传后的文件名，请勿带目录，否则可能导致文件上传失败。
	 */
	public void uploadFileTo(String absoluteFile, String filepath, String filename) throws IOException{
		//创建输入流
		try(InputStream inputStream = new FileInputStream(absoluteFile)) {
			//执行上传操作
			uploadFileTo(inputStream, filepath, filename);
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 上传文件到FTP。（指针跟随上传目录移动）
	 * @param inputStream 输入流
	 * @param filepathname 保存的文件名。
	 */
	public void uploadFileTo(InputStream inputStream, String filepathname) throws IOException{
		//解析名称
		String[] pathName = parseFilepath(filepathname);
		//执行上传操作
		uploadFileTo(inputStream, pathName[0], pathName[1]);
	}
	
	/**
	 * 上传文件到FTP。（指针跟随上传目录移动）
	 * @param inputStream 输入流
	 * @param filepath 要上传到FTP的目录。
	 * @param filename 上传后的文件名
	 */
	public void uploadFileTo(InputStream inputStream, String filepath, String filename) throws IOException{
		try {
			//自动初始化
			init();
			//进入目录。如果目录不存在，则自动创建
			super.changeDirectoryMake(filepath);
			//保存文件
			super.storeFile(filename, inputStream);
		} catch (IOException e) {
			//发生异常，试图关闭连接
			logoutQuietly();
			//打印异常
			throw e;
		}
	}
	
	/**
	 * 将FTP上的文件下载到指定目录下。（指针跟随下载目录移动）
	 * @param filepathname FTP上的文件。
	 * @param saveDirectory 要保存的本地目录
	 */
	public void downloadFileTo(String filepathname, String saveDirectory) throws IOException{
		//解析名称
		String[] pathName = parseFilepath(filepathname);
		//下载
		downloadFileTo(pathName[0], pathName[1], saveDirectory);
	}
	
	/**
	 * 将FTP上的文件下载到指定输出流
	 * @param filepathname FTP上的文件。
	 * @param outputStream 目标输出流。数据写入后不会自动关闭，需要手动关闭
	 */
	public void downloadFileTo(String filepathname, OutputStream outputStream) throws IOException{
		//解析名称
		String[] pathName = parseFilepath(filepathname);
		//下载
		downloadFileTo(pathName[0], pathName[1], outputStream);
	}
	
	/**
	 * 将FTP上的文件下载到指定目录下。（指针跟随下载目录移动）
	 * @param pathname 文件所在FTP目录。
	 * @param filename FTP上要下载的文件
	 * @param saveDirectory 要保存到本地的目录位置
	 */
	public void downloadFileTo(String pathname, String filename, String saveDirectory) throws IOException{
		//本地文件对象
		File file = new File(saveDirectory + "/" + filename);
		//输出流
		try(OutputStream os = new FileOutputStream(file)) {
			//执行下载
			downloadFileTo(pathname, filename, os);
		}
	}

	/**
	 * 将FTP上的文件下载到指定目录下。（指针跟随下载目录移动）
	 * @param pathname 文件所在FTP目录。
	 * @param filename FTP上要下载的文件
	 * @param outputStream 目标输出流。数据写入后不会自动关闭，需要手动关闭
	 */
	public void downloadFileTo(String pathname, String filename, OutputStream outputStream) throws IOException{
		try {
			//初始化
			init();
			//切换目录
			super.changeWorkingDirectory(pathname);
			//读取目录下所有文件
			FTPFile[] ftpfiles = super.listFiles();
			//遍历下载文件
			for(FTPFile ftpfile : ftpfiles){ 
				//如果是要下载的文件
				if(filename.equals(ftpfile.getName())){
					//下载文件
					super.retrieveFile(filename, outputStream);
					//退出
					break;
                }
			}
		} catch (IOException e) {
			//试图关闭连接
			logoutQuietly();
			//抛出异常
			throw e;
		}
	}
	
	/**
	 * 解析包含路径的文件名
	 * @param filepathname 包含路径的文件名
	 * @return
	 */
	private String[] parseFilepath(String filepathname) {
		//索引
		int index = filepathname.lastIndexOf("/");
		//路径
		String filepath = index>-1 ? (index==0 ? "/" : filepathname.substring(0, index)) : "";
		//文件名
		String filename = index>-1 ? filepathname.substring(index+1) : filepathname;
		//返回
		return new String[] {filepath, filename};
	}
	
	/**
	 * 静默登出
	 * @return
	 */
	public boolean logoutQuietly() {
		try {
			return super.logout();
		}catch(IOException e) {
			Logger.error("登出时发生异常：" + e.getMessage(), e);
			return false;
		}
	}
}
